import ConfigParser, re, logging, time
from devio import Switches, Relays
from scene import Scene, Transition
from timers import Timer
from owfs import Owfs, OWFSException

logger = logging.getLogger(__name__)


class Sensor(object):
    "Base class for all 1-wire sensors"

    _family = ""

    def __init__(self, addr, alias):
        self.family = addr[:2]
        self.addr = addr
        self.alias = alias
        self.owfs = Owfs()

    def __str__(self):
        return "alias: %s, addr: %s, family: %s" % (
        self.alias,
        self.addr,
        self.family
        )

    @staticmethod
    def isFamily(addr, family):
        return addr.find(family, 0, 2) >= 0

    def read(self, path):
        return self.owfs.read(self.addr + "/" + path)

    def readAlarm(self, path):
        return self.owfs.read("alarm/" + self.addr + "/" + path)

    def write(self, path, value):
        self.owfs.write(self.addr + "/" + path, value)

    def logInitException(self, e):
        logger.error("IO exception during initialization of %s: %s" % (self.alias, e))

    #parese config file
    def parse(self):
        pass

    #send initial commands to sensor
    def config(self):
        #set alias
        try:
            self.write("alias", self.alias)
            logger.debug("set alias: %s" % self.alias)
        except OWFSException, e:
            self.logInitException(e)

    def init(self):
        self.parse()
        self.config()

    def handleAlarms(self):
        logger.debug("Alarm: %s" % self.alias)
        pass

    def handleTimers(self):
        pass


class HobbyBoardsHub(Sensor):
    "Wrapper for HobbyBoards 4-port OW hub"

    _family = "EF"

    def __init__ (self, addr, alias, c):
        super(HobbyBoardsHub, self).__init__(addr, alias)
        self.mask = 0
        self.cfg = c

    def __str__(self):
        return "alias: %s, addr: %s, family: %s, mask: %x" % (
        self.alias,
        self.addr,
        self.family,
        self.mask
        )

    @staticmethod
    def isFamily(addr):
        return Sensor.isFamily(addr, HobbyBoardsHub._family)

    def parse(self):
        super(HobbyBoardsHub, self).parse()
        section = self.alias
        try:
            self.mask = self.cfg.parser.getint(section, "maskport")
        except ConfigParser.NoOptionError:
            self.mask = 0
            logger.warning("No port mask for hub; all ports blocked")
            pass
        logger.debug("mask: %x" % self.mask)

    def config(self):
        super(HobbyBoardsHub, self).config()
        try:
            self.write("hub/branch.BYTE", str(self.mask))
        except OWFSException, e:
            self.logInitException(e)


class Switch(Sensor):
    "Wrapper class for DS2408 - inputs"
    "Switch is responsible for handling alarms and states of inputs of DS2408"
    "It has related relay or scene"

    #const
    _family = "29"
    _monostable = 0
    _bistable = 1
    _redir = {
        Switches.Pir : Relays.Rly1,
        Switches.Sw1 : Relays.Rly1,
        Switches.Sw2 : Relays.Rly2,
        Switches.Sw3 : 0,
        Switches.Sw4 : 0
    }
    _hold = {
        "no" : 0,
        "sustain" : 1,
        "scene" : 2
    }
    _idleTime = 0.5
    _holdTime = 1
    
    def __init__ (self, addr, alias, sw, c):
        super(Switch, self).__init__(addr, alias)
        self.cfg = c
        self.name = sw
        self.sw = eval("Switches." + sw)
        self.type = Switch._bistable    #_bistable changes state of related relay; monostable always switches on and sets relay's sustain timer
        self.sustain = 10
        self.hold = 0
        self.scene = None #name string
        #timers
        self.timerHold = Timer()    #how long the switch was held
        self.timerHold.cancel()
        self.timerIdle = Timer()    #time from last alarm
        #status
        self.sensed = 0
        #default redirection
        self.redir = Relay(addr, self.alias, Relays.names[Switch._redir[self.sw]])

    def __str__(self):
        return "alias: %s, addr: %s, family: %s, name: %s, type: %s, sustain: %d, hold: %d%s, redir: %s.%s" % (
        self.alias,
        self.addr,
        self.family,
        self.name,
        ["monostable", "bistable"][self.type],
        self.sustain,
        self.hold,
        "" if not self.hold else " (sustain)" if self.sustain else " (" + self.scene + ")" if self.scene is not None else "",
        self.redir.alias, self.redir.name
        )

    @staticmethod
    def isFamily(addr):
        return Sensor.isFamily(addr, Switch._family)

    def parse(self):
        super(Switch, self).parse()
        section = self.alias + "." + self.name
        try:
            self.type = eval("Switch._" + self.cfg.stripTail(self.cfg.parser.get(section, "type", "bistable")))
        except ConfigParser.NoOptionError:
            pass
        try:
            self.sustain = self.cfg.parser.getint(section, "sustain")
        except ConfigParser.NoOptionError:
            pass
        try:
            self.hold = Switch._hold[self.cfg.stripTail(self.cfg.parser.get(section, "hold", "no"))]
        except ConfigParser.NoOptionError:
            pass
        try:
            self.scene = self.cfg.stripTail(self.cfg.parser.get(section, "scene", ""))
        except ConfigParser.NoOptionError, e:
            if self.hold == Switch._hold["scene"]:
                raise e
            pass
        try:
            v = self.cfg.stripTail(self.cfg.parser.get(section, "redir", ""))
            if re.match(".+\..*", v):
                r = re.split("\.", v)
                self.redir = Relay(self.cfg.aliases[r[0]], r[0], r[1])
            else:
                self.redir = Relay(self.addr, self.alias, re.split("[\s#]", v)[0])
        except ConfigParser.NoOptionError:
            pass
        #TODO: scene

    def config(self):
        super(Switch, self).config()
        try:
            self.write("out_of_testmode", "1")
            self.write("PIO.BYTE", "0")
            self.write("por", "0")
            self.write("set_alarm", "133333333")
        except OWFSException, e:
            self.logInitException(e)

    @staticmethod
    def resetAlarm(addr):
        #reset latch
        Owfs().write(addr + "/latch.BYTE", "0")

    def handleAlarms(self, latch, sensed):
        return False

    def handleAlarms(self):
        super(Switch, self).handleAlarms()
        latch = int(self.readAlarm("latch.BYTE"))
        #self.df.alarm(file, latch)     #TODO
        logger.debug("Latch: %02X" % latch)
        por = 0
        try:
            por = int(self.readAlarm("por"))
        except:
            pass
        if not por:
            if (latch & self.sw):
                logger.debug(self.alias + " -> %s.%s" % (self.redir.alias, self.redir.name))

                sensed = True if ((~int(self.read("sensed.BYTE"))) & self.sw) else False    #all signals negative
                logger.debug("sensed: %d" % sensed)

                if self.sensed: #previous alarm
                    logger.debug("sensed[%s]: %x" % (self.alias, self.sensed))

                if sensed:  #now high
                    if not self.sensed:     #previously low -> raising edge
                        logger.debug("raising edge")    #toggle bistable, detect hold
                        self.handleRaisingEdge()
                    else:
                        #falling ana immediate raising edge
                        logger.debug("falling edge")
                        self.handleFallingEdge()
                        logger.debug("raising edge")
                        self.handleRaisingEdge()
                else:   #now low
                    if self.sensed: #previously high -> falling edge
                        logger.debug("falling edge")
                        self.handleFallingEdge()
                    else: #previously low -> raising and immediate falling edge
                        logger.debug("raising edge")
                        self.handleRaisingEdge()
                        logger.debug("falling edge")
                        self.handleFallingEdge()

                
                if 0:
    
    
                    #TODO: if scene configured, react on falling edge
                    if not sensed:# or (self.cfg.devices[file].activeScene() and file in self.sensed and self.sensed[file] & sw and not sensed & sw):
                        if self.type == Switch._bistable or self.redir.sustained:
                            logger.debug("self.timerIdle.expired(): %d" % self.timerIdle.expired())
                            if self.timerIdle.expired():
                                self.timerIdle.setTimeoutS(1)
                                self.redir.toggle()
                                self.redir.sustained = False
                        else:
                            self.redir.on(self.sustain)
                    if sensed:
                        if not self.sensed:
                            if self.hold:
                                #create timer (hold)
                                logger.debug("detecting 1s HOLD...")
                                self.timerHold.setTimeoutS(1)
                            self.sensed = True
                    else:
                        if self.sensed:
                            logger.debug("cancelling timerHold %s" % self.alias)
                            self.timerHold.cancel()
                        self.sensed = False
        else:   #por
            self.owfs.write(file + "/por", "0")
            self.owfs.write(file + "/set_alarm", "133333333")
        return latch & self.sw

    def handleTimers(self):
        super(Switch, self).handleTimers()
        if self.timerHold.expired():
            logger.debug("HOLD detected! %s:%s" % (self.alias, self.name))
            logger.debug("hold: %d" % self.hold)
            if self.hold == Switch._hold["sustain"]:
                self.redir.sustain()
            elif self.hold == Switch._hold["scene"]:
                #self.playScene(self.config.devices[name].scene)
                logger.info("play scene %s: %s" % (self.name, "scene_name"))
                pass


    def handleRaisingEdge(self):
        self.sensed = True
        logger.debug("self.timerIdle.expired(): %d" % self.timerIdle.expired())
        if self.timerIdle.expired():    # toggle frequency filter
            self.timerIdle.setTimeoutS(Switch._idleTime)
            if self.hold != Switch._hold["scene"]:
                if self.type == Switch._bistable or self.redir.sustained:
                    self.redir.toggle()
                    self.redir.sustained = False
                else:   #monostable
                    self.redir.on(self.sustain)
            if self.hold:
                logger.debug("detecting %ds HOLD..." % Switch._holdTime)
                self.timerHold.setTimeoutS(Switch._holdTime)

        pass

    def handleFallingEdge(self):
        self.sensed = False
        if self.hold == Switch._hold["scene"]:
            if not self.timerHold.expired():
                if self.type == Switch._bistable or self.redir.sustained:
                    self.redir.toggle()
                    self.redir.sustained = False
                else:   #monostable
                    self.redir.on(self.sustain)
        if self.hold:
            logger.debug("cancelling timerHold %s" % self.alias)
            self.timerHold.cancel()

        pass

    def activeScene(self):
        logger.debug("activeScene(): " + (self.hold and self.scene != None))
        return self.hold and self.scene != None



class Relay(Sensor):
    "Wrapper class for DS2408 - simple relay outputs"
    "Relay is an executive component related to sensor / switch"
    def __init__(self, addr, alias, rly):
        super(Relay, self).__init__(addr, alias)
        self.name = rly
        self.rly = eval("Relays." + rly)
        #timers
        self.timerOff = Timer()     # for monostable - time to switch off
        self.timerOff.cancel()      # not expired
        #status
        self.sustained = False

    def __str__(self):
        return "alias: %s, addr: %s, family: %s, name: %s, rly: %d" % (
        self.alias,
        self.addr,
        self.family,
        self.name,
        self.rly
        )

    def handleAlarms(self):
        super(Relay, self).handleAlarms()

    def handleTimers(self):
        super(Relay, self).handleTimers()
        if self.timerOff.expired():
            self.off()

    def on(self, time = 0):
        self.write("PIO." + str(self.rly), "1")
        self.timerOff.setTimeoutS(time)
        self.sustained = not time
        logger.debug("Set relay: %s.%s %s" % (self.alias, self.name, "sustain" if self.sustained else ""))
        return self

    def off(self):
        self.write("PIO." + str(self.rly), "0")
        self.sustained = True;
        logger.debug("Clear relay: %s.%s" % (self.alias, self.name))
        return self

    def toggle(self):
        state = int(self.read("PIO." + str(self.rly)))
        if state:
            self.off()
        else:
            self.on()
        return self

    def sustain(self):
        self.sustained = True
        self.timerOff.cancel()

    def delay(self, s):
        time.sleep(s)
        return self

#legacy
class Device:
    #const
    _monostable = 0
    _bistable = 1

    #vars
    def __init__ (self, addr):
        self.alias = ""
        self.addr = addr
        self.type = Device._bistable
        self.delay = 10
        self.hold = False
        self.scene = None #name string
        self.sustain = False
        self.redir = {}
        self.redir[Switches.Pir] = (addr, Relays.Rly1)
        self.redir[Switches.Sw1] = (addr, Relays.Rly1)
        self.redir[Switches.Sw2] = (addr, Relays.Rly2)
        self.redir[Switches.Sw3] = (addr, 0)
        self.redir[Switches.Sw4] = (addr, 0)

    def activeScene(self):
        logger.debug("activeScene(): " + (self.hold and self.scene != None))
        return self.hold and self.scene != None

    def __str__(self):
        return "alias: %s, addr: %s, type: %s, delay: %s, hold: %d%s\nredir: %s" % (
        self.alias,
        self.addr,
        ["monostable", "bistable"][self.type],
        self.delay,
        self.hold,
        " " if not self.hold else " (sustain)" if self.sustain else " (" + self.scene + ")" if self.scene is not None else " ",
        self.redir
        )


